package servernode

import (
	"acs/comet/config"
	"acs/eventbroker"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"strings"
	"time"

	log "github.com/cihub/seelog"
	"github.com/cloudfoundry/gosigar"
)

var logger = log.Default

// SetLogger  replace the default seelog.Default
func SetLogger(newLogger log.LoggerInterface) {
	logger = newLogger
}

// nodeId 实例的唯一标识,为md5字符串
var nodeId = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%v", time.Now().UnixNano()))))

//NodeInfoUpdateTick 单位: 秒
const NodeInfoUpdateTick = 10

// RegisterNode 注册配置服务节点信息,并保持定期更新。 服务退出时删除注册信息, 如果非正常退出,则20秒内注册信息过期。
func RegisterNode(bindAddr string, regServerEndpoints []string, authInfoStr string, monitorRoot string, exitSigChan chan bool) error {
	authInfo := strings.Split(authInfoStr, ":")
	etcdCfg := eventbroker.Config{
		Endpoints: regServerEndpoints,
	}
	if len(authInfo) > 1 {
		etcdCfg.Username = authInfo[0]
		etcdCfg.Password = authInfo[1]
	}
	// testRemoteAddr := ""
	// if len(regServerEndpoints) < 1 {
	// 	testRemoteAddr = eventbroker.DefaultBrokerAddr
	// } else {
	// 	// 从https地址中获取ip:port
	// 	testRemoteAddr = strings.Trim(regServerEndpoints[0], "htps:/")
	// }
	//	localAddr, err := testLocalAddr(testRemoteAddr)

	// if err != nil {
	// 	return errors.New("Failed to get local addr: " + err.Error())
	// }
	localAddr := config.Conf.WnetAddress
	localIp := strings.Split(localAddr, ":")[0]
	client, err := eventbroker.NewClient(etcdCfg)
	if err != nil {
		return err
	}
	nodeInfoChan := make(chan Node, 1)
	infoUpdater, err := newNodeInfoUpdater(bindAddr, localIp)
	if err != nil {
		return err
	}
	go infoUpdater.loopUpdate(nodeInfoChan)
	go func() {
		defer func() {
			err := recover()
			if err != nil {
				logger.Errorf("RegisterNode info updater fatal: %v", err)
			}
		}()
		registerNode := strings.TrimRight(monitorRoot, "/") + "/" + nodeId

		for {
			select {
			case nodeInfo := <-nodeInfoChan:
				d, _ := json.Marshal(nodeInfo)
				logger.Debugf("Registering node info: %s", d)
				err = client.SetWithTTL(registerNode, string(d), time.Second*(NodeInfoUpdateTick*2))
				if err != nil {
					logger.Errorf("Failed to update RegisterNode info: %v", err)
				}
			case <-exitSigChan:
				client.Delete(registerNode)
				return
			}
		}
	}()
	return nil
}

// testLocalAddr 通过测试连接获取本地地址
func testLocalAddr(remoteAddr string) (string, error) {
	conn, err := net.DialTimeout("tcp", "www.baidu.com:80", time.Second*2)
	if err != nil {
		conn, err = net.DialTimeout("tcp", remoteAddr, time.Second*5)
		if err != nil {
			return "", err
		}
	}
	defer conn.Close()
	return conn.LocalAddr().String(), nil
}

type nodeInfoUpdater struct {
	loadAvg  sigar.LoadAverage
	memUsage sigar.Mem
	localIp  string
	bindAddr string
}

func newNodeInfoUpdater(bindAddr, localIp string) (*nodeInfoUpdater, error) {
	concreteSigar := sigar.ConcreteSigar{}
	loadAvg, err := concreteSigar.GetLoadAverage()
	if err != nil {
		return nil, errors.New("Failed to get loadaverage: " + err.Error())
	}
	memusage, err := concreteSigar.GetMem()
	if err != nil {
		return nil, errors.New("Failed to get loadaverage: " + err.Error())
	}
	return &nodeInfoUpdater{
		localIp:  localIp,
		bindAddr: bindAddr,
		memUsage: memusage,
		loadAvg:  loadAvg,
	}, nil
}

// TODO: io stats not set
func (niu *nodeInfoUpdater) loopUpdate(nodeInfoChan chan Node) {
	node := Node{
		Ip:       niu.localIp,
		BindAddr: niu.bindAddr,
		Info: NodeInfo{
			RegTime: uint64(time.Now().Unix()),
			Load:    LoadAverage{niu.loadAvg.One, niu.loadAvg.Five, niu.loadAvg.Fifteen},
			Mem:     MemInfo{niu.memUsage.Total, niu.memUsage.Used, niu.memUsage.Free, niu.memUsage.ActualFree, niu.memUsage.ActualUsed},
		},
	}
	nodeInfoChan <- node
	ticker := time.Tick(time.Second * NodeInfoUpdateTick)
	var err error
	for {
		t := <-ticker
		err = niu.loadAvg.Get()
		if err != nil {
			logger.Errorf("Failed to get loadaverrage: %v", err)
			continue
		}

		err = niu.memUsage.Get()
		if err != nil {
			logger.Errorf("Failed to get loadaverrage: %v", err)
			continue
		}
		node.Info.Load.One = niu.loadAvg.One
		node.Info.Load.Five = niu.loadAvg.Five
		node.Info.Load.Fifteen = niu.loadAvg.Fifteen
		node.Info.Mem.Total = niu.memUsage.Total
		node.Info.Mem.Used = niu.memUsage.Used
		node.Info.Mem.Free = niu.memUsage.Free
		node.Info.Mem.ActualFree = niu.memUsage.ActualFree
		node.Info.Mem.ActualUsed = niu.memUsage.ActualUsed
		node.Info.UpdateTime = uint64(t.Unix())
		nodeInfoChan <- node
	}
}
